//****************************************************************************************
// Name:		xp_create_master_key
// Platform:	SQL Server 2000 SP3a or higher, Windows NT, 2000 or XP
// Author:		Copyright (c) 2006 by Michael Coles, MCDBA
//
// Description:	This extended stored procedure generates a master key for a database.  The
//				format is EXEC xp_generate_master_key @password, @key OUTPUT [, @password2]
//
// @password is the first parameter.  It is a user-defined password of up to 128 bytes.
// the key is created by hashing this password.  If NULL is passed in, a 128 byte password
// is generated randomly.
//
// @key is an OUTPUT parameter, and the second param.  This parameter must be at least
// 512 bits (64 bytes).  It should be VARBINARY or BINARY (NOTE:  VARCHAR and CHAR will 
// strip some trailing non-printable characters from your keys.)
//
// @password2 is the third (optional) parameter.  If this parameter is included, the key
// will be encrypted by using a hash of this password parameter.  If this parameter is not
// included (or NULL), the key will be encrypted using a hash of the currently logged on
// user credentials.
//
// LEGAL STUFF:
// ------------
// Copyright (C) 2005 - 2006 by Michael Coles, MCDBA
//
// Some included code included is released under the redistribution agreements as 
// specified by the authors of the respective code.  Copyright holders of this included 
// code maintain all copyright and other rights to their original or derivative works.
//
// All rights reserved.                          
//
// REDISTRIBUTION OF THIS CODE:
// ----------------------------
// All code included in this package is either the original work of the copyright holder,
// or derivative work based on other copyright holders' works.  All derivative works 
// include information as required by the copright holders' redistribution agreements.
// These redistribution agreements, where possible, are included in the text of the source
// code distributed with this code.
//
// Redistribution and use in source and binary forms, with or without modification, are 
// permitted provided that the following conditions are met:
//
//   1. Redistributions of source code must retain the above copyright notice, this list 
//      of conditions and the following disclaimer.
//
//   2. Redistributions in binary form must reproduce the above copyright notice, this 
//      list of conditions and the following disclaimer in the documentation and/or other 
//      materials provided with the distribution.
//
//   3. The names of its contributors may not be used to endorse or promote products 
//      derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT 
// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 
// WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//****************************************************************************************

#include <dblib.h>
#include <params.h>
#include <constants.h>
#include <sha2.h>
#include <twister.h>
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#include <wincrypt.h>      // CryptoAPI definitions
#include <stdexcept>

#ifdef __cplusplus
extern "C" {
#endif

RETCODE __declspec(dllexport) xp_create_master_key(SRV_PROC *srvproc);

#ifdef __cplusplus
}
#endif

RETCODE __declspec(dllexport) xp_create_master_key(SRV_PROC *srvproc)
{
	// Define parameters
	params *P1 = new params();
	params *P2 = new params();
	params *P3 = new params();
	// rc is the XP return code
	RETCODE rc = XP_NOERROR;
	int numparams = params::getparamcount(srvproc); // number of params
	unsigned char password[128];	// the password passed in (param 1)
	ULONG passwordlength = 0;		// the password length
	unsigned char key[64];			// the key generated
	char password2[128];			// the second password (param 2)
	ULONG password2length = 0;		// length of param 2
	PBYTE pBuffer = NULL;
	DWORD dwSize = 0;
	try
	{
		if (numparams == 3) { // If a third parameter (2nd password) is passed in, use it to encrypt the key
			params::getparam(srvproc, 3, P3);
			if (P3->isoutput) { // Third param cannot be OUTPUT
				Dblib::printerror(srvproc, USAGE_CREATE_MASTER_KEY);
				rc = XP_ERROR;
			} else if (!P3->isnull) { // If parameter is not NULL, grab the third param
				srv_bzero(password2, 128);
				memcpy(password2, P3->cdata, P3->length);
				password2length = P3->length;
			} else { // If third parameter is NULL
				srv_bzero(password2, 128);
				strcpy(password2, srv_pfield(srvproc, SRV_USER, (int *)NULL));
				strupr(password2);
				password2length = (int)strlen(password2);
			}
		} else { // Otherwise, with 2 params, grab the user name and use it to encrypt the key
			srv_bzero(password2, 128);
			strcpy(password2, srv_pfield(srvproc, SRV_USER, (int *)NULL));
			strupr(password2);
			password2length = (int)strlen(password2);
		}
		if (rc == XP_NOERROR) {
			if (numparams == 2 || numparams == 3) { // Must have 2 or 3 params
				params::getparam(srvproc, 1, P1);
				if (!P1->isoutput) // Param 1 cannot be output
				{
					if (P1->isnull) // If Param 1 is NULL, generate a random password
					{
						MTRand mtr;
						mtr.seed();
						for (int j = 0; j < 16; j++) {
							UINT32 i = mtr.randInt();
							password[j * 4] = (char)i >> 24;
							password[j * 4 + 1] = (char)(i >> 16) & 255;
							password[j * 4 + 2] = (char)(i >> 8) & 255;
							password[j * 4 + 3] = (char)(i & 255);
						}
						passwordlength = 128;
					} else { // Otherwise, use the password passed in
						srv_bzero(password, 128);
						passwordlength = (P1->length & 127);
						memcpy(password, (unsigned char *)P1->cdata, passwordlength);
					}
				} else {
					Dblib::printerror(srvproc, USAGE_CREATE_MASTER_KEY);
					rc = XP_ERROR;
				}
				if (rc == XP_NOERROR) {
					params::getparam(srvproc, 2, P2); // Param 2 is the output @key param
					if (!P2->isoutput && rc == XP_NOERROR) {
						Dblib::printerror(srvproc, USAGE_CREATE_MASTER_KEY);
						rc = XP_ERROR;
					} else if (P2->maxlength < 64) { // Param 2 must be able to hold 64 bytes
						Dblib::printerror(srvproc, USAGE_CREATE_MASTER_KEY);
						rc = XP_ERROR;
					}
				}
			} else {
				Dblib::printerror(srvproc, USAGE_CREATE_MASTER_KEY);
				rc = XP_ERROR;
			}
		}
		if (rc == XP_NOERROR) {	// We made it this far...
			srv_bzero(key, 64);	// Zero out the key
			sha512_ctx ctx[1];	// Create an SHA-512 hash
			sha512_begin(ctx);
			sha512_hash(password, passwordlength, ctx);
			sha512_end(key, ctx);
			BOOL bResult = false;
			HCRYPTPROV hProv = NULL;
			if(CryptAcquireContext(		// Grab a Crypto API Context
				&hProv,					// handle to the CSP
				SCRYPTO_KEY_CONTAINER,	// container name 
				NULL,					// use the default provider
				PROV_RSA_FULL,			// provider type
				CRYPT_MACHINE_KEYSET))	// flag values
			{
				//Dblib::printmessage(srvproc, "A new Crypto API cryptographic context acquired.");
			} else {
				if (GetLastError() == NTE_BAD_KEYSET) {
					if	(CryptAcquireContext( // Create a new Crypto API Context
						&hProv, 
						SCRYPTO_KEY_CONTAINER, 
						NULL, 
						PROV_RSA_FULL, 
						CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET)) 
					{
						//Dblib::printmessage(srvproc, "A new key Crypto API container has been created.");
					} else {
						Dblib::printerror(srvproc, "Could not create a new MS Crypto API key container.");
						rc = XP_ERROR;
					}
				}
				else
				{
					Dblib::printerror(srvproc, "An MS Crypto API cryptographic service handle could not be acquired.");
					rc = XP_ERROR;
				}
  			}
			if (rc == XP_NOERROR) {
				HCRYPTKEY hKey = NULL;
				HCRYPTHASH hHash = NULL;
				// Create a hash object.
				if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) {
					// Create a CryptoAPI hash object
					Dblib::printerror(srvproc, "Could not create a Crypto API Hash object.");
					rc = XP_ERROR;
				} 
				if (rc == XP_NOERROR && !CryptHashData(hHash, (CONST BYTE *)password2, password2length, 0)) {
					// Create a CryptoAPI hash of the password
					Dblib::printerror(srvproc, "Could not create SHA1 hash using Crypto API.");
					rc = XP_ERROR;
				}
				if (rc == XP_NOERROR && !CryptDeriveKey(hProv, CALG_RC4, hHash, 0, &hKey)) {
					// Derive a session key using the Crypto API
					Dblib::printerror(srvproc, "Could not derive a key using Crypto API.");
					rc = XP_ERROR;
				}
				if (rc == XP_NOERROR) {
					// Set variable to length of data in buffer.
					dwSize = 64;
					// Have API return us the required buffer size.
					bResult = CryptEncrypt(
								hKey,            // Key obtained earlier
								0,               // No hashing of data
								TRUE,            // Final or only buffer of data
								0,               // Must be zero
								NULL,            // No data yet, simply return size
								&dwSize,         // Size of data
								dwSize);         // Size of block
					// We now have a size for the output buffer, so create buffer.
					// We do this in case the encrypted size will be larger than
					// 512 bits.
					pBuffer = new BYTE[dwSize];
					memcpy(pBuffer, key, 64);
					// Now encrypt data.
					bResult = CryptEncrypt(		// Perform the actual encryption
								hKey,			// Key obtained earlier
								0,				// No hashing of data
								TRUE,			// Final or only buffer of data
								0,              // Must be zero
								pBuffer,		// Data buffer
								&dwSize,		// Size of data
								dwSize);		// Size of block
					// Clean up
					CryptDestroyKey(hKey);
					CryptDestroyHash(hHash);
					CryptReleaseContext(hProv, 0);
					// Return the encrypted key
					srv_paramsetoutput (srvproc, 2, (BYTE *)pBuffer, (DBINT)dwSize, false);
				}
			}
		}
	} catch(std::exception ex) {
		Dblib::printerror(srvproc, ERR_CRYPT_EXCEPTION);
		rc = XP_ERROR;
	}
	if (rc == XP_NOERROR) {
		srv_senddone(srvproc, (SRV_DONE_MORE), (DBSMALLINT)0, (DBINT)0);
	} else {
		srv_senddone(srvproc, (SRV_DONE_MORE | SRV_DONE_ERROR), (DBSMALLINT)0, (DBINT)0);
	}
	// Done.  Zero out memory and clean up pointers
	srv_bzero(password, 128);
	srv_bzero(key, 64);
	srv_bzero(password2, 128);
	if (pBuffer != NULL) {
		srv_bzero(pBuffer, dwSize);
		delete [] pBuffer;
	}
	pBuffer = NULL;
	if (P1 != NULL) {
		srv_bzero(P1->cdata, P1->length);
		delete P1;
	}
	P1 = NULL;
	if (P2 != NULL) {
		srv_bzero(P2->cdata, P2->length);
		delete P2;
	}
	P2 = NULL;
	if (P3 != NULL) {
		srv_bzero(P3->cdata, P3->length);
		delete P3;
	}
	P3 = NULL;
	return rc;
}